<?php
namespace juven\libs {
    \defined( '_JEXEC' ) or die( 'Restricted access' );
    
    class Router {
        const EVENT_onWarning_UnknownHttpRequestMethod       = "event_onWarning_UnknownHttpRequestMethod";
        const EVENT_onWarning_ControllerFileNotFound         = "event_onWarning_ControllerFileNotFound";
        const EVENT_onWarning_ActionNotFound                 = "event_onWarning_ActionNotFound";
        const EVENT_onWarning_PermissionDenied               = "event_onWarning_PermissionDenied";
        const EVENT_onWarning_ControllerNameValidationFailed = "event_onWarning_ControllerNameValidationFailed";
        const EVENT_onWarning_ActionNameValidationFailed     = "event_onWarning_ActionNameValidationFailed";
        const EVENT_onWarning_ParamsValidationFailed         = "event_onWarning_ParamsValidationFailed";
        
        protected $_controllerName = \NULL;
        protected $_actionName = \NULL;
        protected $_params = array();
        protected $_eventsHandlerPointer = \NULL;
        
        public function __construct(){
            $this->_eventsHandlerPointer = function(){ @\error_log(\json_encode(\func_get_args())); };
            juven::eventsManager()->subscribe(self::EVENT_onWarning_UnknownHttpRequestMethod,       new EventCallback($this->_eventsHandlerPointer));
            juven::eventsManager()->subscribe(self::EVENT_onWarning_ControllerFileNotFound,         new EventCallback($this->_eventsHandlerPointer));
            juven::eventsManager()->subscribe(self::EVENT_onWarning_ActionNotFound,                 new EventCallback($this->_eventsHandlerPointer));
            juven::eventsManager()->subscribe(self::EVENT_onWarning_PermissionDenied,               new EventCallback($this->_eventsHandlerPointer));
            juven::eventsManager()->subscribe(self::EVENT_onWarning_ControllerNameValidationFailed, new EventCallback($this->_eventsHandlerPointer));
            juven::eventsManager()->subscribe(self::EVENT_onWarning_ActionNameValidationFailed,     new EventCallback($this->_eventsHandlerPointer));
            juven::eventsManager()->subscribe(self::EVENT_onWarning_ParamsValidationFailed,         new EventCallback($this->_eventsHandlerPointer));
        }

        public function route(){
            $dtp = new \stdClass();
            $dtp->serverRequestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : "";
            if ($dtp->serverRequestMethod==="GET" || $dtp->serverRequestMethod==="POST"){
                $arr = $this->parse_GET_request();

                // is controller name provided ?
                if (!isset($arr[0]) || $arr[0]==\NULL){ // controller name is not provided, load default controller,action and params
                    return $this->loadDefaultController();
                } 
                $this->_controllerName = array_shift($arr);
                
                // is action name provided ?
                if (!isset($arr[0]) || $arr[0]==\NULL){ // action name is not provided, load default action and params
                    $this->_actionName     = juven::config()->GET_request->defaultActionName;
                    $this->_params         = array();
                } else {
                    $this->_actionName = array_shift($arr);
                    $this->_params     = $arr; // here we have the params, if any.
                }
                
                $cv = $this->validate_ControllerName($this->_controllerName);
                if ($cv!==\TRUE){ // if controller name validation fail, load 404
                    $dtp1 = new \stdClass();
                    $dtp1->ValidationError             = $cv;
                    $dtp1->GETRequest                  = \implode("/", $_REQUEST);
                    juven::eventsManager()->publish(self::EVENT_onWarning_ControllerNameValidationFailed, $dtp1);
                    return $this->loadPageNotFoundController();
                }
                
                $ctrl_fileNamePath = juven::config()->controllersFolderPath.$this->getPrefixed_ControllerName($this->_controllerName).".php";
                if (!\is_readable($ctrl_fileNamePath)){
                    juven::eventsManager()->publish(self::EVENT_onWarning_ControllerFileNotFound, 
                            "WARNING::Controller's file not found or is not readable, requested controller:".$ctrl_fileNamePath);
                    return $this->loadPageNotFoundController();
                }
                
                $av = $this->validate_ActionName($this->_actionName);
                if ($av!==\TRUE){ // if action name validation fail, load 404
                    $dtp1 = new \stdClass();
                    $dtp1->ValidationError             = $av;
                    $dtp1->GETRequest                  = \implode("/", $_REQUEST);
                    juven::eventsManager()->publish(self::EVENT_onWarning_ActionNameValidationFailed, $dtp1);
                    return $this->loadPageNotFoundController();
                }
                
                $pv = $this->validate_Params($this->_params);
                if ($pv!==\TRUE){
                    $dtp1 = new \stdClass();
                    $dtp1->ValidationError             = $pv;
                    $dtp1->GETRequest                  = \implode("/", $_REQUEST);
                    juven::eventsManager()->publish(self::EVENT_onWarning_ParamsValidationFailed, $dtp1);
                    return $this->loadPageNotFoundController();
                }
                
                return $this->loadController($this->_controllerName, $this->_actionName, $this->_params);
            } else {
                juven::eventsManager()->publish(self::EVENT_onWarning_UnknownHttpRequestMethod, 
                        "WARNING::Unknown http request method:".$dtp->serverRequestMethod);
                return $this->loadPageNotFoundController();
            }
        }

        protected function loadController($controllerName, $actionName, array $params){
            $controllerClassName = "juven\\controllers\\".$this->getPrefixed_ControllerName($controllerName);
            if (!\method_exists($controllerClassName, $this->getPrefixed_ActionName($actionName))){
                if ($this->getPrefixed_ActionName($actionName)!=="action_"){
                    $dtp1 = new \stdClass();
                    $dtp1->ErrorMsg   = "WARNING::Action [".  $this->getPrefixed_ActionName($actionName)."] does not exists in Controller:".
                            juven::config()->controllersFolderPath.$this->getPrefixed_ControllerName($controllerName).".php";
                    $dtp1->GETRequest = \implode("/", $_REQUEST);
                    juven::eventsManager()->publish(self::EVENT_onWarning_ActionNotFound, $dtp1);
                    return $this->loadPageNotFoundController();
                } else {
                    // if $prefixedActionName = 'action_' it means that the user did not provided actionName, 
                    // user requested just the controller, so try to load the default action...
                    $actionName = juven::config()->GET_request->defaultActionName;
                }
            }
            $auth = juven::permissionsManager()->authHttpGETRequest(juven::user(), $controllerName, $actionName, $params);
            if ($auth!==\TRUE){
                $dtp1 = new \stdClass();
                $dtp1->AuthError  = $auth;
                $dtp1->GETRequest = \implode("/", $_REQUEST);
                juven::eventsManager()->publish(self::EVENT_onWarning_PermissionDenied, $dtp1);
                return $this->loadPagePermissionDeniedController();
            }
            $ctrl_obj = new $controllerClassName($this->getPrefixed_ActionName($actionName), $params);
            $ctrl_obj->{$this->getPrefixed_ActionName($actionName)}($params); 
            return \TRUE;
        }
        
        public function loadDefaultController(){
            $dtp = new \stdClass();
            $dtp->_prefexedControllerName = $this->getPrefixed_ControllerName(juven::config()->GET_request->defaultControllerName); 
            $dtp->_prefixedActionName     = $this->getPrefixed_ActionName(juven::config()->GET_request->defaultActionName);
            $dtp->_params                 = array();
            
            $dtp->ctrl_fileNamePath = juven::config()->controllersFolderPath.$dtp->_prefexedControllerName.".php";
            if (!\is_readable($dtp->ctrl_fileNamePath)){
                juven::eventsManager()->publish(self::EVENT_onWarning_ControllerFileNotFound, 
                        "WARNING::The file of the Controller [".$dtp->ctrl_fileNamePath."] not found or is not readable");
                \header('HTTP/1.0 404 Not Found'); echo "<h1>404: Page not found</h1>";
                die();
            }
                
            $controllerClassName = "juven\\controllers\\".$dtp->_prefexedControllerName;
            $ctrl_obj = new $controllerClassName($dtp->_prefixedActionName, $dtp->_params);
            $ctrl_obj->{$dtp->_prefixedActionName}($dtp->_params); 
            return \TRUE;
        }
        
        public function loadPageNotFoundController(){
            $dtp = new \stdClass();
            $dtp->_prefexedControllerName = $this->getPrefixed_ControllerName(juven::config()->GET_request->defaultPageNotFound404ControllerName); 
            $dtp->_prefixedActionName     = $this->getPrefixed_ActionName(juven::config()->GET_request->defaultActionName);
            $dtp->_params                 = array();
            
            $dtp->ctrl_fileNamePath = juven::config()->controllersFolderPath.$dtp->_prefexedControllerName.".php";
            if (!\is_readable($dtp->ctrl_fileNamePath)){
                juven::eventsManager()->publish(self::EVENT_onWarning_ControllerFileNotFound, 
                        "WARNING::The file of the Controller [".$dtp->ctrl_fileNamePath."] not found or is not readable");
                \header('HTTP/1.0 404 Not Found'); echo "<h1>404: Page not found</h1>";
                die();
            }
                
            $controllerClassName = "juven\\controllers\\".$dtp->_prefexedControllerName;
            $ctrl_obj = new $controllerClassName($dtp->_prefixedActionName, $dtp->_params);
            $ctrl_obj->{$dtp->_prefixedActionName}($dtp->_params); 
            return \TRUE;
        }
        
        public function loadPagePermissionDeniedController(){
            $dtp = new \stdClass();
            $dtp->_prefexedControllerName = $this->getPrefixed_ControllerName(juven::config()->GET_request->defaultPermissionDenied); 
            $dtp->_prefixedActionName     = $this->getPrefixed_ActionName(juven::config()->GET_request->defaultActionName);
            $dtp->_params                 = array();
            
            $dtp->ctrl_fileNamePath = juven::config()->controllersFolderPath.$dtp->_prefexedControllerName.".php";
            if (!\is_readable($dtp->ctrl_fileNamePath)){
                juven::eventsManager()->publish(self::EVENT_onWarning_ControllerFileNotFound, 
                        "WARNING::The file of the Controller [".$dtp->ctrl_fileNamePath."] not found or is not readable");
                \header('HTTP/1.0 404 Not Found'); echo "<h1>404: Page not found</h1>";
                die();
            }
                
            $controllerClassName = "juven\\controllers\\".$dtp->_prefexedControllerName;
            $ctrl_obj = new $controllerClassName($dtp->_prefixedActionName, $dtp->_params);
            $ctrl_obj->{$dtp->_prefixedActionName}($dtp->_params); 
            return \TRUE;
        }
        
        public function getPrefixed_ControllerName($controllerName){ return (string)"ctrl_".$controllerName; }
        
        public function getPrefixed_ActionName($actionName){ return (string)"action_".$actionName; }
        
        /** @return array  */
        public function parse_GET_request(){
            $reqFieldName = juven::config()->GET_request->fieldName;
            $url = isset($_GET[$reqFieldName]) ? $_GET[$reqFieldName] : \NULL;
            $url = \rtrim($url, '/');
            $url = \filter_var($url, \FILTER_SANITIZE_URL);
            return \explode('/', $url);
        }
        
        /**
         * @param string $controllerName
         * @return boolean|String Returns TRUE on success validation or a string message with error description, on error
         */
        public function validate_ControllerName($controllerName){
            $n = (string)$controllerName;
            $len = \strlen($n);
            if ($len<juven::config()->GET_request->controllerName_minLength || $len>juven::config()->GET_request->controllerName_maxLength){
                return "ControllerName length out of bounds";
            }

            for ($i = 0; $i < $len; $i++) {
                if (!\in_array(\substr($n, $i, 1), juven::config()->GET_request->controllerName_allowedChars)) { 
                    return "ControllerName contains invalid characters";
                }
            }
            return \TRUE;
        }

        /**
         * @param string $actionName
         * @return boolean|String Returns TRUE on success validation or a string message with error description, on error
         */
        public function validate_ActionName($actionName){
            $n = (string)$actionName;
            if ($n=="_init"){ return "Requested ActionName is a reserved keyword"; }
            $len = \strlen($n);
            if ($len>juven::config()->GET_request->actionName_maxLength){ return "ActionName length out of bounds"; }
            for ($i = 0; $i < $len; $i++) {
                if (!\in_array(\substr($n, $i, 1), juven::config()->GET_request->actionName_allowedChars)) { 
                    return "ActionName contains invalid characters";
                }
            }
            return \TRUE;
            
        }

        /**
         * @param array $params
         * @return boolean|String Returns TRUE on success validation or a string message with error description, on error
         */
        public function validate_Params(array $params){
            if (\count($params)>juven::config()->GET_request->maximumNumberOfParameters){
                return "The number of parameters exceed the defined limit [".juven::config()->GET_request->maximumNumberOfParameters."]";
            }
            foreach ($params as $key => $value){
                $n = (string)$value;
                $len = \strlen($n);
                if ($len>juven::config()->GET_request->params_maxLength){ return "Parameters length out of bounds"; }
                for ($i = 0; $i < $len; $i++) {
                    if (!\in_array(\substr($n, $i, 1), juven::config()->GET_request->params_allowedChars)){ 
                        return "Parameter contains invalid characters";
                    }
                }
            }
            return \TRUE;
        }
    }
}
?>
